package com.bsj.power.pls.handle;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bsj.power.common.config.cached.RedisCached;
import com.bsj.power.common.config.redis.RedisConstant;
import com.bsj.power.common.config.rocketmq.producer.BaseMqPushUtil;
import com.bsj.power.common.constant.MqTopicAndTagConstants;
import com.bsj.power.common.def.vo.WebSocketMsgVO;
import com.bsj.power.common.util.ExceptionUtil;
import com.bsj.power.common.util.SpringUtil;
import com.bsj.power.pls.config.net.netty.gpgga.GpggaParsingData;
import com.bsj.power.pls.config.net.netty.gpgga.GpggaTcpClientConstant;
import com.bsj.power.pls.config.net.netty.gpgga.GpggaTcpClientNet;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author LiJiaXin
 * @version 1.0.0
 * @description 1号机组Gpgga数据处理
 * @time 2024/11/27 12:08
 */
@Slf4j
public class GpggaHandle extends SimpleChannelInboundHandler<String> {

    private int unitNo;

    private static RedisCached redisCached;
    private static BaseMqPushUtil baseMqPushUtil;

    static {
        redisCached = SpringUtil.getBean(RedisCached.class);
        baseMqPushUtil = SpringUtil.getBean(BaseMqPushUtil.class);
    }

    public GpggaHandle(int unitNo) {
        this.unitNo = unitNo;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String str) throws Exception {
        if (StringUtils.isNotBlank(str)) {
            // 分析数据
            List<GpggaParsingData> gpggaParsingDatas = parserData(str);
            //将解析的每一条有效GPGGA的E、N、U、经度、纬度、高度数据以及X方向坐标、Y方向坐标、Z方向坐标）；实时显示在网页右侧的对应位置
            //防止频率太快只取最后一条推送给前端
            if (!CollectionUtils.isEmpty(gpggaParsingDatas)) {
                GpggaParsingData gpggaParsingData = gpggaParsingDatas.get(gpggaParsingDatas.size() - 1);
                gpggaParsingData.setUnitNo(unitNo);
                //推送给前端
                WebSocketMsgVO webSocketMsgVO = WebSocketMsgVO.builder()
                        .msgType(9)
                        .obj(gpggaParsingData)
                        .build();
                baseMqPushUtil.sendTopicBytes(MqTopicAndTagConstants.SYSTEM_WEB_SERVER_CHANGE_TOPIC,
                        MqTopicAndTagConstants.SYSTEM_WEB_PRODUCT_CHANGE_TAG, JSON.toJSONBytes(webSocketMsgVO));
            }
        }
    }

    /**
     * 解析GPGGA数据
     *
     * @param str
     * @return java.util.List<com.bsj.power.pls.gpgga.GpggaParsingData>
     * @author ljx
     * @time 2024/11/27 17:30
     */
    private List<GpggaParsingData> parserData(String str) {
        log.info(unitNo + "号机组接收到GPGGA数据：{}", str);
        List<GpggaParsingData> result = new LinkedList<>();
        String shapeVariableStr = redisCached.get(RedisConstant.SHAPE_VARIABLE_INFO_KEY + unitNo);
        if (StringUtils.isBlank(shapeVariableStr)) {
            log.info("形变量为空");
            return result;
        }
        List<Double> shapeVariable = Arrays.stream(JSONObject.parseObject(shapeVariableStr, String.class).split(",")).map(Double::parseDouble).collect(Collectors.toList());
        try {
            str = str.substring(str.indexOf('$'));
            List<String> list = Arrays.stream(str.split("\\$")).map(s -> "$" + s).collect(Collectors.toList());
            for (String item : list) {
                if (item.indexOf("GGA") >= 0) {
                    GpggaParsingData data = getGpggaParsingData(item);
                    if (data == null) {
                        continue;
                    }
                    data.setTimestamp(System.currentTimeMillis());
                    //判断GPGGA数据是否有效
                    if (data.getHorizontalDilution() < 4 || data.getNumSatellites() > 4) {
                        //定位无效
                        if (data.getLatitude() == 0 || data.getLongitude() == 0 || data.getAltitude() == 0) {
                            continue;
                        } else if (data.getQuality() == 4) {
                            //判断是否进入差分定位模式
                            //入参为上一时刻的经度纬度高度以及解析的GPGGA得到的经度纬度高度，共六个（已改为固定的经纬高，不再使用上一时刻的经纬高）
                            //出参为ENU方向的误差值，解析GPGGA得到的经度纬度高度转成的XYZ值
                            double[] array = assessPos(shapeVariable.get(0), shapeVariable.get(1), shapeVariable.get(2), data.getLongitude(), data.getLatitude(), data.getAltitude());
                            double e = array[0];
                            double n = array[1];
                            double u = array[2];
                            //过滤掉少数不合理的数据
                            if (e >= 1000 || e <= -1000) {
                                continue;
                            }
                            if (n >= 1000 || n <= -1000) {
                                continue;
                            }
                            if (u >= 1000 || u <= -1000) {
                                continue;
                            }
                            data.setE(e);
                            data.setN(n);
                            data.setU(u);
                            data.setX(array[3]);
                            data.setY(array[4]);
                            data.setZ(array[5]);
                            result.add(data);
                            //缓存gpgga数据
                            redisCached.lpush(RedisConstant.GPGGA_DATA_INFO_KEY + unitNo, JSON.toJSONString(data));
                            log.info(unitNo + "号GPGGA数据缓存成功：{}", data);
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("解析GPGGA数据失败:{}", str);
        }
        return result;
    }

    /**
     * 此时解析的这条GPGGA位置数据与上一时间段内的数据的位置数据的ENU三个方向的误差函数
     *
     * @param stdlon 上一时间段的经度
     * @param stdlat 上一时间段的纬度
     * @param stdalt 上一时间段的高度
     * @param lon    经度
     * @param lat    纬度
     * @param alt    高度
     * @return double[]
     */
    private double[] assessPos(double stdlon, double stdlat, double stdalt, double lon, double lat, double alt) {
        stdlon = stdlon * Math.PI / 180;
        stdlat = stdlat * Math.PI / 180;
        lon = lon * Math.PI / 180;
        lat = lat * Math.PI / 180;

        //计算位置偏差
        double[] xyz1 = lla2xyz(stdlon, stdlat, stdalt);
        double[] xyz2 = lla2xyz(lon, lat, alt);
        double deltaX = xyz2[0] - xyz1[0];
        double deltaY = xyz2[1] - xyz1[1];
        double deltaZ = xyz2[2] - xyz1[2];

        double lon1 = stdlon;//参考经度
        double lat1 = stdlat;//参考纬度
        double alt1 = stdalt;//参考高度

        double cosLat1 = Math.cos(lat1);
        double sinLat1 = Math.sin(lat1);
        double cosLon1 = Math.cos(lon1);
        double sinLon1 = Math.sin(lon1);

        double[] enu = new double[6];
        enu[0] = cosLon1 * deltaY - sinLon1 * deltaX;//东
        enu[1] = cosLat1 * deltaZ - sinLat1 * cosLon1 * deltaX - sinLat1 * sinLon1 * deltaY;//北
        enu[2] = cosLat1 * cosLon1 * deltaX + cosLat1 * sinLon1 * deltaY + sinLat1 * deltaZ;//天
        enu[3] = xyz2[0];//X
        enu[4] = xyz2[1];//Y
        enu[5] = xyz2[2];//Z
        return enu;
    }

    /**
     * 经度纬度高度转XYZ
     *
     * @param lon 经度
     * @param lat 纬度
     * @param alt 高度
     * @return double[]
     * @author ljx
     * @time 2024/11/27 19:56
     */
    private double[] lla2xyz(double lon, double lat, double alt) {
        double Ra = 6378137;//GPS and BD2，椭球长半轴长,m
        double Ec2 = 6.69437999013e-3;//EC2为地球标准椭球的第一偏心率EC的平方，常量
        double dLon = lon;
        double dLat = lat;
        double dAlt = alt;
        double sinLat = Math.sin(dLat);
        double cosLat = Math.cos(dLat);
        double sinLon = Math.sin(dLon);
        double cosLon = Math.cos(dLon);

        //计算卯酉圈半径rn
        double rn = Ra / Math.sqrt(1 - Ec2 * sinLat * sinLat);//EC2为地球标准椭球的第一偏心率EC的平方，常量
        //Ra为地球标准椭球的长半轴，常量

        //计算直角坐标下x
        double x = (rn + dAlt) * cosLat * cosLon;

        //计算直角坐标下y
        double y = (rn + dAlt) * cosLat * sinLon;

        //计算直角坐标下Z
        double z = (rn * (1 - Ec2) + dAlt) * sinLat;
        return new double[]{x, y, z};
    }

    /**
     * 解析数据
     *
     * @param str
     */
    private GpggaParsingData getGpggaParsingData(String str) {
        GpggaParsingData gpggaParsingData = new GpggaParsingData();
        int i = 1;
        String[] split = str.split(",");
        if (split.length < 10) {
            return gpggaParsingData;
        }
        //时间
        gpggaParsingData.setTime(split[i]);

        //纬度
        i += 1;
        try {
            gpggaParsingData.setLatitude(min2Degree(split[i]));
        } catch (Exception e) {
            gpggaParsingData.setLatitude(0);
        }

        //N
        i += 1;
        gpggaParsingData.setLatitudeDirection(split[i]);

        //经度
        i += 1;
        try {
            gpggaParsingData.setLongitude(min2Degree(split[i]));
        } catch (Exception e) {
            gpggaParsingData.setLongitude(0);
        }

        //E
        i += 1;
        gpggaParsingData.setLongitudeDirection(split[i]);

        //定位质量指示，判断单点还是差分
        i += 1;
        try {
            gpggaParsingData.setQuality(Integer.parseInt(split[i]));
        } catch (Exception e) {
            gpggaParsingData.setQuality(-1);
        }

        //使用卫星数量
        i += 1;
        try {
            gpggaParsingData.setNumSatellites(Integer.parseInt(split[i]));
        } catch (Exception e) {
            gpggaParsingData.setNumSatellites(-1);
        }

        //水平精度因子
        i += 1;
        try {
            gpggaParsingData.setHorizontalDilution(Double.parseDouble(split[i]));
        } catch (Exception e) {
            gpggaParsingData.setHorizontalDilution(0);
        }

        //海拔高度
        i += 1;
        try {
            gpggaParsingData.setAltitude(Double.parseDouble(split[i]));
        } catch (Exception e) {
            gpggaParsingData.setAltitude(0);
        }

        //单位（M 跳过）
        i += 2;
        try {
            gpggaParsingData.setAltitudeUnits(Double.parseDouble(split[i]));
        } catch (Exception e) {
            gpggaParsingData.setAltitudeUnits(0);
        }
        return gpggaParsingData;
    }

    /**
     * 度分转度
     *
     * @param str
     * @return
     */
    private double min2Degree(String str) {
        double d = Double.parseDouble(str);
        d /= 100;
        //整数
        int integer = (int) Math.floor(d);
        //小数
        double decimals = d - integer;
        return integer + decimals * 100 / 60;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info(unitNo + "号机组GPGGA client ctx = {}", ctx);
    }

    /**
     * 处理异常, 一般是需要关闭通道
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error(ExceptionUtil.getStackStr(cause));
        ctx.close();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);

        log.info(unitNo + "号机组GPGGA与服务器断开");
        ctx.close();
        //重连
        GpggaTcpClientNet tcpClientNet = new GpggaTcpClientNet(GpggaTcpClientConstant.getIp(this.unitNo),
                GpggaTcpClientConstant.getPort(this.unitNo), this.unitNo);
        tcpClientNet.initClient();
        tcpClientNet.doConnect();
    }

}
